import type { MapMouseEvent } from 'mapbox-gl';
import type Mapless from '..';
import { genPrefixId } from '../core/manager';
import type Shape from './shape/base';
import Circle from './shape/circle';
import CircleRadius from './shape/circle-radius';
import Freedom from './shape/freedom';
import Rectangle from './shape/rectangle';
import Select from './shape/select';
import { getPolygon, type Paint, type PaintMode } from './utils';

// TODO select模式下对图形进行移动
class PaintBoard {
  mapless: Mapless | undefined;
  map: mapboxgl.Map | null;

  sourceId = '';
  layerId = '';

  isDrawing = false;
  drawingMode = 'select';
  paints: Paint[] = [];

  maxCount = 100;

  constructor(mapless: Mapless, options?: { maxCount?: number }) {
    this.mapless = mapless;
    this.map = mapless.map;
    this.maxCount = options?.maxCount ?? 100;
    this.initLayers();
  }

  onDrawingStart: (paint: Paint | undefined) => void = (_paint) => {};
  onDrawing: (paint: Paint | undefined) => void = (_paint) => {};
  onDrawingEnd: (paint: Paint | undefined) => void = (_paint) => {};
  onDrawingRemove: (paint: Paint | undefined) => void = (_paint) => {};
  onDrawingChange: (paint: Paint | undefined) => void = (_paint) => {};

  shapesMap = new Map<string, Shape>();
  getShape(mode: string) {
    const hasShape = this.shapesMap.has(mode);
    if (!hasShape && this.map) {
      if (mode === 'freedom') {
        this.shapesMap.set(mode, new Freedom(this.map));
      } else if (mode === 'circle') {
        this.shapesMap.set(mode, new Circle(this.map));
      } else if (mode === 'rectangle') {
        this.shapesMap.set(mode, new Rectangle(this.map));
      } else if (mode === 'select') {
        this.shapesMap.set(mode, new Select(this.map));
      } else if (mode === 'circle-radius') {
        this.shapesMap.set(mode, new CircleRadius(this.map));
      }
    }
    return this.shapesMap.get(mode);
  }

  listeners = {
    mousedown: (e: mapboxgl.MapMouseEvent): void => {
      this.map?.dragPan.disable();
      this.isDrawing = true;
      const paint = this.getShape(this.drawingMode)?.DrawingStart(e, this);
      if (this.isValidatePaint(paint)) {
        this.onDrawingStart?.(paint);
        if (paint && this.drawingMode === 'select') {
          paint.selected = true;
          this.draw2Map();
        }
      }
    },
    mousemove: (e: mapboxgl.MapMouseEvent): void => {
      if (this.isDrawing) {
        const paint = this.getShape(this.drawingMode)?.Drawing(e, this);
        if (this.isValidatePaint(paint)) {
          this.onDrawing?.(paint);
        }
      }
    },
    mouseup: (e: mapboxgl.MapMouseEvent): void => {
      this.map?.dragPan.enable();
      this.isDrawing = false;
      const paint = this.getShape(this.drawingMode)?.DrawingEnd(e, this);
      if (this.isValidatePaint(paint)) {
        if (this.drawingMode !== 'select') {
          if (paint) {
            paint.id = `_${Date.now()}`;
            this.paints.push(paint);
            if (this.paints.length > this.maxCount) {
              this.onDrawingRemove?.(this.paints.shift());
            }
          }
        } else {
          if (paint) {
            paint.selected = false;
          }
        }
        this.draw2Map();
        this.onDrawingEnd?.(paint);
      }
    },
  };

  isValidatePaint(paint: Paint | undefined) {
    if (!paint) return false;
    if (
      paint.type === 'rectangle' ||
      paint.type === 'circle' ||
      paint.type === 'circle-radius'
    ) {
      const first = paint.coordinates[0];
      const second = paint.coordinates[1];
      return !(first[0] === second[0] && first[1] === second[1]);
    }
    return paint.coordinates.length >= 3;
  }

  initLayers() {
    if (!this.map) return;

    this.sourceId = genPrefixId('dzmap-paint-source');
    this.layerId = genPrefixId('dzmap-paint-layer');

    this.map.addSource(this.sourceId, {
      type: 'geojson',
      data: {
        type: 'FeatureCollection',
        features: [],
      },
    });
    this.map.addLayer({
      id: this.layerId,
      type: 'fill',
      source: this.sourceId,
      paint: {
        'fill-color': 'rgba(128,128,128,0.2)',
        'fill-outline-color': '#f00',
        'fill-opacity': ['case', ['==', ['get', 'selected'], true], 0.3, 1],
      },
    });

    this.map.on('click', this.clickHandler);
  }

  clickHandler = (e: MapMouseEvent) => {
    const features = this.map?.queryRenderedFeatures(e.point, {
      layers: [this.layerId],
    });
    if (features?.length) {
      const feature = features[0];
      const pid = feature.properties?.paintId;
      const fnd = this.paints.find((p) => p.id === pid);
      fnd && this.onDrawingEnd?.(fnd);
    } else {
      // 点击了空白
      this.onDrawingEnd?.(undefined);
    }
  };

  draw2Map() {
    // 绘制到地图
    const source = this.map?.getSource(this.sourceId) as mapboxgl.GeoJSONSource;
    const features = this.paints.map((p) => {
      const poly = getPolygon(p);
      if (poly && this.drawingMode === 'select') {
        if (poly.properties) {
          poly.properties.selected = p.selected;
        }
      }
      if (poly?.properties) {
        poly.properties.paintId = p.id;
      }
      return poly;
    }) as GeoJSON.Feature[];
    source?.setData?.({
      type: 'FeatureCollection',
      features,
    });
  }

  setPaintMode(mode: PaintMode) {
    this.drawingMode = mode;
    if (this.map) {
      this.map.getCanvas().style.cursor =
        mode === 'select' ? 'default' : 'crosshair';
    }
  }

  startDraw() {
    if (!this.map) return;
    this.map.on('mousedown', this.listeners.mousedown);
    this.map.on('mousemove', this.listeners.mousemove);
    this.map.on('mouseup', this.listeners.mouseup);
  }

  clearDraw() {
    this.paints = [];
    this.draw2Map();
  }

  exitDraw() {
    if (!this.map) return;
    this.drawingMode = 'select';
    this.map.getCanvas().style.cursor = '';
    this.map.off('mousedown', this.listeners.mousedown);
    this.map.off('mousemove', this.listeners.mousemove);
    this.map.off('mouseup', this.listeners.mouseup);
  }

  destroy() {
    if (!this.map) return;
    try {
      this.map.off('click', this.clickHandler);
      this.map.removeLayer(this.layerId);
      this.map.removeSource(this.sourceId);
    } catch (_error) {
      // console.log(error);
    }
  }
}

export default PaintBoard;
